class Game {
    constructor() {
        this.container = document.querySelector("#container");
        this.scoreDOM = document.querySelector("#score");
        this.delay = 200;
        this.score = 0;
        this.initBoard();
        this.initListener();
    }

    resetGame() {
        this.data =
            [0, 0, 0, 0,
                0, 0, 0, 0,
                0, 0, 0, 0,
                0, 0, 0, 0];
        this.score = 0;

        this.addNumber();
        this.addNumber();

        this.updateBoard();
    }

    initBoardParams() {
        let boardDOM = document.querySelector("#container");
        this.boardWidth = boardDOM.clientHeight;
        this.gap = 10;
        this.cellWidth = (this.boardWidth - this.gap * 5) / 4;
    }

    initBoard() {
        this.initBoardParams();

        for (let index = 0; index < 16; index++) {
            let [top, left, i, j] = this.getPosOfNumCell(index);
            let cellDOM = document.querySelector(`#cell_${i}_${j}`);

            cellDOM.style.top = top;
            cellDOM.style.left = left;
            cellDOM.style.width = this.cellWidth +"px";
            cellDOM.style.height = this.cellWidth +"px";
        }
    }

    getPosOfNumCell(index) {
        let j = index % 4;
        let i = (index - j) / 4;

        let top = (this.gap + i * (this.cellWidth + this.gap)) + "px";
        let left = (this.gap + j * (this.cellWidth + this.gap)) + "px";

        return [top, left, i, j];
    }

    updateScore() {
        this.scoreDOM.innerHTML = this.score;
    }

    generateNumber() {
        let zeros = 0;

        this.data.forEach(v => {
            if (v === 0) zeros++;
        });

        let index = Math.ceil(Math.random() * (zeros)) - 1;
        let num = Math.random() > 0.5 ? 4 : 2;

        for (let i = this.data.length - 1; i >= 0; i--) {
            if (this.data[i] === 0) {
                if (index === 0) {
                    index = i;
                    break;
                }
                index--;
            }
        }

        return [index, num];
    }

    addNumber() {
        let [index, num] = this.generateNumber();
        this.data[index] = num;
    }

    removeAllNumCells() {
        document.querySelectorAll(".number_cell").forEach(dom => dom.remove());
    }

    clcFontSize(v){
        let size = 60;
        let space = this.cellWidth - 20;
        
        if(v<10){
            size = space - 20 > 60 ? 60 : space - 20;
        }else if(v<100){
            size = space / 1.5;
        }else if(v<1000){
            size = space / 2;
        }else if(v<10000){
            size = space / 2.5;
        }

        return size;
    }

    addNumCell(v, index) {
        let [top, left, i, j] = this.getPosOfNumCell(index);
        let bg_color = this.getNumCellBackgroundColor(v);
        let color = this.getNumCellColor(v);

        let size = this.clcFontSize(v);

        let domStr = `<div class="number_cell" id="number_cell_${i}_${j}" style="width: ${this.cellWidth}px; height: ${this.cellWidth}px; line-height: ${this.cellWidth}px; top: ${top}; font-size: ${size}px; left: ${left}; background-color: ${bg_color}; color: ${color}; transition: top ${this.delay / 1000}s ease, left ${this.delay / 1000}s ease;">${v}</div>`;
        let dom = document.createElement("div");
        dom.innerHTML = domStr;

        this.container.appendChild(dom.childNodes[0]);
    }

    getNumCellBackgroundColor(value) {
        switch (value){
        case 2: return "#eee4da";
        case 4: return "#ede0c8";
        case 8: return "#f2b179";
        case 16: return "#f59563";
        case 32: return "#f67c5f";
        case 64: return "#f65e3b";
        case 128: return "#edcf72";
        case 256: return "#edcc61";
        case 512: return "#9c0";
        case 1024: return "#33b5e5";
        case 2048: return "#09c";
        case 4096: return "#a6c";
        case 8192: return "#93c";
        }
        
        return "black";
    }

    getNumCellColor(value) {
        if (value <= 4)
            return "#776e65";
        return "white";
    }

    updateBoard() {
        this.removeAllNumCells();

        this.data.forEach((v, index) => {
            if (v === 0) return;
            this.addNumCell(v, index);
        });

        this.updateScore();
    }

    move(traverse, getPreCell) {
        traverse((v, index, i, j) => {

            let pre = getPreCell(v, i, j);
            let dom = document.querySelector(`#number_cell_${i}_${j}`);
            if (!dom) return;

            let [top, left, ,] = this.getPosOfNumCell(pre);

            if (this.data[pre] === 0) {
                this.data[pre] = v;
                this.data[index] = 0;
            } else if (this.data[pre] === v && pre !== index) {
                this.score += v;
                this.data[pre] = v + v;
                this.data[index] = 0;
                dom.innerHTML = this.data[pre];
            }

            dom.style.top = top;
            dom.style.left = left;
        });
    }

    moveTop() {
        this.move((cb) => {
            for (let i = 1; i < 4; i++) {
                for (let j = 0; j < 4; j++) {
                    let index = i * 4 + j;
                    let v = this.data[index];

                    cb(v, index, i, j);
                }
            }
        }, (v, i, j) => {
            i = i - 1;
            while (i >= 0) {
                let index = i * 4 + j;
                let tmp = this.data[index];
                if (tmp === 0) {
                    if (i === 0) break;
                    i = i - 1;
                } else if (tmp === v) {
                    break;
                } else {
                    i += 1;
                    break;
                }
            }
            return i * 4 + j;
        });
    }
    
    moveRight() {
        this.move((cb) => {
            for (let i = 0; i < 4; i++) {
                for (let j = 2; j >= 0; j--) {
                    let index = i * 4 + j;
                    let v = this.data[index];

                    cb(v, index, i, j);
                }
            }
        }, (v, i, j) => {

            j = j + 1;

            while (j <= 3) {
                let index = i * 4 + j;
                let tmp = this.data[index];

                if (tmp === 0) {
                    if (j === 3) break;
                    j = j + 1;
                } else if (tmp === v) {
                    break;
                } else {
                    j -= 1;
                    break;
                }

            }

            return i * 4 + j;
        });
    }

    moveBottom() {
        this.move((cb) => {
            for (let i = 2; i >= 0; i--) {
                for (let j = 0; j < 4; j++) {
                    let index = i * 4 + j;
                    let v = this.data[index];

                    cb(v, index, i, j);
                }
            }
        }, (v, i, j) => {

            i = i + 1;

            while (i <= 3) {
                let index = i * 4 + j;
                let tmp = this.data[index];

                if (tmp === 0) {
                    if (i === 3) break;
                    i = i + 1;
                } else if (tmp === v) {
                    break;
                } else {
                    i -= 1;
                    break;
                }

            }

            return i * 4 + j;
        });

    }

    moveLeft() {
        this.move((cb) => {
            for (let i = 0; i < 4; i++) {
                for (let j = 1; j < 4; j++) {
                    let index = i * 4 + j;
                    let v = this.data[index];

                    cb(v, index, i, j);
                }
            }
        }, (v, i, j) => {

            j = j - 1;

            while (j >= 0) {
                let index = i * 4 + j;
                let tmp = this.data[index];

                if (tmp === 0) {
                    if (j === 0) break;
                    j = j - 1;
                } else if (tmp === v) {
                    break;
                } else {
                    j += 1;
                    break;
                }

            }

            return i * 4 + j;
        });
    }

    initListener() {
        window.addEventListener("keydown", (event) => {

            switch (event.keyCode) {
            case 38:
                // top
                this.moveTop();
                break;
            case 39:
                // right
                this.moveRight();
                break;
            case 40:
                // bottom
                this.moveBottom();
                break;
            case 37:
                // left
                this.moveLeft();
                break;
            default:
                return;
            }

            this.addNumber();
            setTimeout(() => this.updateBoard(), this.delay);
        });
    }
}